// AutoRunMgr.cpp

/*

Copyright (c) 2007 OpenNETCF Consulting, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
and associated documentation files (the "Software"), to deal in the Software without restriction, 
including without limitation the rights to use, copy, modify, merge, publish, distribute, 
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or 
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.

*/

#include <windows.h>
#include "storemgr.h"
#include "pnp.h"

// allows x86 to link without setting project properties
// you might need to alter these for non-x86 builds
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/nodefaultlib:libcd.lib")

// constants
#define QUEUE_NAME			(_T("AUTORUNMGR_QUEUE"))
#define MAX_MSG_SIZE		(MAX_PATH + sizeof(DEVDETAIL))
#define INSERT_CMD_LINE		(_T("inserted"))
#define REMOVE_CMD_LINE		(_T("removed"))
#define AUTORUN_FILENAME	(_T("Autorun.exe"))

// forward declarations
BOOL WaitForNotification(HANDLE hNotify, HANDLE hQueue);
void OnInsert(TCHAR *diskName);
void OnRemove(TCHAR *diskName);
void CreateFileName(TCHAR *diskName, TCHAR *fileName, DWORD size);

// declarations so we link
extern "C" BOOL GetSystemMemoryDivision( 
  LPDWORD lpdwStorePages, 
  LPDWORD lpdwRamPages, 
  LPDWORD lpdwPageSize 
);
extern "C" BOOL SetSystemMemoryDivision(DWORD StorePages);
extern "C" BOOL IsAPIReady(DWORD hAPI);

int _tmain(int argc, _TCHAR* argv[])
{
	while(! IsAPIReady(SH_WMGR))
	{
		Sleep(100);
	}

	if(argc > 1)
	{
		int launchCode = _ttoi(argv[1]);
		SignalStarted(launchCode);
	}

	// create msg queue
	MSGQUEUEOPTIONS options;
	ZeroMemory(&options, sizeof(options));

	options.dwSize			= sizeof(options);
	options.bReadAccess		= TRUE;
	options.cbMaxMessage	= 0;
	options.dwFlags			= 0;
	options.cbMaxMessage	= MAX_MSG_SIZE;

	HANDLE hQueue = CreateMsgQueue(QUEUE_NAME, &options);
	if(hQueue == NULL) return -1;

	// request device notifications
	HANDLE hNotify = RequestDeviceNotifications(&FATFS_MOUNT_GUID, hQueue, TRUE);
	if(hNotify == NULL) return -2;

	while(WaitForNotification(hNotify, hQueue))
	{
		// spin forever unless we have an error
	}

	return 0;
}

BOOL WaitForNotification(HANDLE hNotify, HANDLE hQueue)
{
	// wait for notifications
	DWORD ret = WaitForSingleObject(hQueue, INFINITE);
	if(ret != WAIT_OBJECT_0) 
	{
		int err = GetLastError();
		// this will cause a full app exit
		return FALSE;
	}

	TCHAR message[MAX_MSG_SIZE];
	DEVDETAIL *detail = (DEVDETAIL*)message;
	ZeroMemory(message, MAX_MSG_SIZE);

	DWORD read = MAX_MSG_SIZE;
	DWORD flags = 0;

	// get the message off the queue
	if(! ReadMsgQueue(hQueue, detail, read, &read, 0, &flags)) 
	{
		// go back and wait for another notification
		return TRUE;
	}

	if(detail->fAttached)
	{
		// inserted
		OnInsert(detail->szName);
	}
	else
	{
		// removed
		OnRemove(detail->szName);
	}

	return TRUE;
}

void OnInsert(TCHAR *diskName)
{
	// does Autorun.exe exist on the volume?
	TCHAR srcFileName[MAX_PATH];
	_tcscpy(srcFileName, diskName);
	_tcscat(srcFileName, _T("\\"));
	_tcscat(srcFileName, AUTORUN_FILENAME);
	if(GetFileAttributes(srcFileName) == INVALID_FILE_ATTRIBUTES)
	{
		// no file, nothing to do
		return;
	}

	// create a volume-specific name
	TCHAR destFileName[MAX_PATH];
	CreateFileName(diskName, destFileName, MAX_PATH);

	// make sure that file doesn't exist in \Windows
	DWORD attributes = GetFileAttributes(destFileName);
	if(attributes != INVALID_FILE_ATTRIBUTES)
	{
		// file exists if it's read-only, clear that
		if(attributes & FILE_ATTRIBUTE_READONLY)
		{
			attributes &= ~FILE_ATTRIBUTE_READONLY;
			SetFileAttributes(destFileName, attributes);
		}

		if(!DeleteFile(destFileName))
		{
			// error condition - we can't delete it
			return;
		}
	}
	
	// copy it to \Windows
	if(!CopyFile(srcFileName, destFileName, FALSE))
	{
		// did we fail due to lack of space?
		if(GetLastError() == ERROR_DISK_FULL)
		{
			// re-partition memory
			DWORD store = 0;
			DWORD ram = 0;
			DWORD pageSize = 0;

			// get the current division
			GetSystemMemoryDivision(&store, &ram, &pageSize);

			// how many pages are needed for the target file?
			HANDLE hFile = CreateFile(srcFileName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
			DWORD fileSize = GetFileSize(hFile, NULL);
			CloseHandle(hFile);
			DWORD neededPages = (fileSize / pageSize) + 1;

			// set the new division
			SetSystemMemoryDivision(store + neededPages);

			// try the copy again
			if(!CopyFile(srcFileName, destFileName, FALSE))
			{
				// error condition - didn't copy.  Not much we can do
				return;
			}
		}
	}

	// execute
	PROCESS_INFORMATION pi;
	if(!CreateProcess(destFileName, INSERT_CMD_LINE, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi))
	{
		// error condition - didn't launch.  Not much we can do
	}
}

void OnRemove(TCHAR *diskName)
{
	// create a volume-specific name
	TCHAR fileName[MAX_PATH];
	CreateFileName(diskName, fileName, MAX_PATH);

	// does file exist in \Windows?
	DWORD attributes = GetFileAttributes(fileName);
	if(attributes == INVALID_FILE_ATTRIBUTES) return;
	else if(attributes & FILE_ATTRIBUTE_READONLY)
	{
		SetFileAttributes(fileName, attributes & ~FILE_ATTRIBUTE_READONLY);
	}
	// run it
	PROCESS_INFORMATION pi;
	if(CreateProcess(fileName, REMOVE_CMD_LINE, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi))
	{
		// wait for exit
		WaitForSingleObject(pi.hProcess, INFINITE);
	}	

	// delete the file, whether is really ran or not
	DeleteFile(fileName);
}

void CreateFileName(TCHAR *diskName, TCHAR *fileName, DWORD size)
{
	_tcscpy(fileName, _T("\\Windows\\"));
	_tcscat(fileName, &diskName[1]);
	_tcscat(fileName, AUTORUN_FILENAME);
}